/******************************************************
 * Distributed Storage Simulator 0.1
 * by Lakshmi Ganesh, Mark Silberstein, and Yang Wang
 * see https://code.google.com/p/ds-sim/
 ******************************************************/
package simulator.unit;
import simulator.*;
import simulator.failure.Trace;

import java.util.Random;
import java.util.HashMap;

public class Machine extends Unit{
    private static Random rand = new Random(); 
    public static double failFraction; // this is the fraction of machine failures that are permanent
    public static boolean eagerRecoveryEnabled;
    public static double failTimeout=-1; // this is the amount of time after which a machine failure is treated as permanent (and eager disk recovery is begun, if eagerRecoveryEnabled is true)
    private static boolean fastForward; // if true, machine failure and recovery events will be generated but ignored (neither handled nor printed to file)

    private static int idCounter=0;
    private int myId;
    @Override
        public void init(String name, Unit parent, HashMap<String, String> parameters){
            myId=idCounter++;
            super.init(name, parent, parameters);
            if (failTimeout==-1){
                this.failFraction = Double.parseDouble(parameters.get("failFraction"));
                this.failTimeout = Double.parseDouble(parameters.get("failTimeout"));
                this.fastForward = Boolean.parseBoolean(parameters.get("fastForward"));
                this.eagerRecoveryEnabled = Boolean.parseBoolean(parameters.get("eagerRecoveryEnabled"));
                System.out.println("Eager recovery: "+(this.eagerRecoveryEnabled ==true ?"enabled":"disabled"));
            }
        }

    public EventGenerator getFailureGenerator(){
        return failureGenerator;
    }

    @Override
        public void generateEvents(EventQueue resultEvents, double startTime, double endTime, boolean reset){
            double currentTime = startTime;
            double lastRecoverTime = startTime;
            if(failureGenerator==null){
                for(Unit u:children){
                    u.generateEvents(resultEvents, startTime, endTime, true);
                }
                return;
            }

            if (failureGenerator instanceof Trace){
                ((Trace)failureGenerator).setCurrentMachine(myId);
            }
            if (recoveryGenerator instanceof Trace){
                ((Trace)recoveryGenerator).setCurrentMachine(myId);
            }

            while(true){
                if (reset) failureGenerator.reset(currentTime);

                if (failureGenerator instanceof Trace){
                    ((Trace)failureGenerator).setCurrentEventType(true); //generate for the event start
                }

                double failureTime = failureGenerator.generateNextEvent(currentTime);
                currentTime = failureTime;
                if(currentTime>endTime){
                    for(Unit u:children){
                        u.generateEvents(resultEvents, lastRecoverTime, endTime, true);
                    }
                    break;
                }

                if (failureGenerator instanceof Trace){
                    ((Trace)failureGenerator).eventAccepted(); //generate for the event start
                }

                for(Unit u:children){
                    u.generateEvents(resultEvents, lastRecoverTime, currentTime, true);
                }
                if (recoveryGenerator instanceof Trace){
                    ((Trace)recoveryGenerator).setCurrentEventType(false); //generate for the event start
                }
                recoveryGenerator.reset(currentTime);
                double recoveryTime = recoveryGenerator.generateNextEvent(currentTime);
                assert (recoveryTime>failureTime);
                if(recoveryTime > endTime-(1E-5)) recoveryTime = endTime-(1E-5); 

                double r = rand.nextDouble();
                int failureType = 0; //failure type: tempAndShort=1, tempAndLong=2, permanent=3
                if(fastForward == false) { // we will process disk failures/eager recoveries generated by this machine failure

                    if(r < failFraction) { // this is a permanent machine failure -- need to generate disk failures
                        failureType = 3;

                        double maxRecoveryTime = recoveryTime;
                        for(Unit u:children) {
                            double diskFailTime = failureTime + (1E-5); // ensure machine fails before disks do
                            Event diskFailEvent = new Event(Event.EventType.Failure, diskFailTime, u);
                            resultEvents.addEvent(diskFailEvent);
                            double diskRecoveryTime = ((DiskWithScrubbing) u).generateRecoveryEvent(resultEvents, diskFailTime, endTime-(1E-5));
                            diskFailEvent.nextRecoveryTime = diskRecoveryTime;
                            if(diskRecoveryTime > maxRecoveryTime) maxRecoveryTime = diskRecoveryTime; // machine recovery must coincide with last disk recovery
                        }
                        recoveryTime = maxRecoveryTime+(1E-5);

                    } else { // this is a temporary machine failure -- may need to generate eager recovery
                        if(recoveryTime-failureTime <= failTimeout)
                            failureType = 1; // this is a temporary machine failure of short duration

                        else {
                            failureType = 2; // this is a temp machine failure of long duration
                            if(eagerRecoveryEnabled) {
                                // we need to trigger eager recovery
                                double eagerRecoveryStartTime = failureTime+failTimeout;
                                Event eagerRecoveryStartEvent = new Event(Event.EventType.EagerRecoveryStart, eagerRecoveryStartTime, this);
                                eagerRecoveryStartEvent.nextRecoveryTime = recoveryTime;
                                resultEvents.addEvent(eagerRecoveryStartEvent);
                                recoveryTime += (1E-5); // Ensure machine recovery happens after last eager recovery installment
                            }
                        }
                    }
                }

                if (failureGenerator instanceof Trace){
                    ((Trace)failureGenerator).eventAccepted(); //generate for the event start
                }

                if(fastForward) {
                    resultEvents.addEvent(new Event(Event.EventType.Failure, failureTime, this, true));
                    resultEvents.addEvent(new Event(Event.EventType.Recovered, recoveryTime, this, true));
                } else {
                    resultEvents.addEvent(new Event(Event.EventType.Failure, failureTime, this, failureType));
                    resultEvents.addEvent(new Event(Event.EventType.Recovered, recoveryTime, this, failureType));
                }
                currentTime = recoveryTime;
                lastRecoverTime = currentTime;
                if(currentTime>=endTime-(1E-5))
                    break;
            }
        }
}
